﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using WoWConnecter.Entities;

namespace WoWConnecter
{
    /// <summary>
    /// Allows a connection to the realmserver
    /// </summary>
    public partial class RealmClient : BaseClient
    {
        private RealmPacketIn mCurrentPacket;

        public uint ClientSeed { get; protected set; }
        public uint ServerSeed { get; protected set; }
        public UserCharacter ActiveCharacter { get; protected set; }
        public UserCharacter[] Characters { get; protected set; }
        public short CharacterCount { get; protected set; }
        public Realm Realm { get; protected set; }

        public PacketCrypt PacketEncryption { get; set; }

        //Events
        public event Action<RealmPacketIn> PacketReceived;
        public event Action<RealmPacketOut> PacketSent;
        public event Action Connected;
        public event Action<UserCharacter[]> CharacterSelection;
        public event Action<LoginErrorCode, Character> CharacterCreationResult;
        public event Action<Character> EnteredWorld;

        private AuthClient authClient;
        private UserCharacter creatingCharacter;
        private bool firstCharEnum;

       internal RealmClient(AuthClient client, SecureRemotePassword securePassword, PacketHandler packetHandler, Realm realm)
            : base(client.Username, client.Password)
       {
            authClient = client;
            SecurePassword = securePassword;
            PacketHandler = packetHandler;
            PacketHandler.Load(this);
            Characters = new UserCharacter[10];
            CharacterCount = 0;
            Realm = realm;
            firstCharEnum = true;
        }

        /// <summary>
        /// Attempts to create a character based on entered information
        /// </summary>
        /// <param name="name"></param>
        /// <param name="race"></param>
        /// <param name="clss"></param>
        /// <returns>Character attempting to create, if there is already a character waiting to be made it will return null</returns>
        public UserCharacter CreateCharacter(string name, RaceId race = RaceId.Human, ClassId clss = ClassId.Warrior)
        {
            if (creatingCharacter != null)
                return null;

            creatingCharacter = new UserCharacter(name, race, clss, 0, 0, 0, 0);
            SendCharacterCreation(creatingCharacter);

            return creatingCharacter;
        }

        /// <summary>
        /// Enters the character into the world
        /// </summary>
        /// <param name="character"></param>
        public void Login(UserCharacter character)
        {
            character.Client = this;
            ActiveCharacter = character;
            PacketHandler.Load(ActiveCharacter);
            ActiveCharacter.StartProcessing(PacketHandler);
            SendCharacterLogin(character);
        }


        /// <summary>
        /// Depreciated
        /// </summary>
        /// <param name="character"></param>
        public void CreateCharacter(UserCharacter character)
        {
            creatingCharacter = character;
            SendCharacterCreation(character);
        }

        /// <summary>
        /// Connects the realm client to the realm
        /// </summary>
        public void Connect()
        {
            int port = 0;

            string[] vars = Realm.Address.Split(':');
            port = int.Parse(vars[1]);

            var ip = Dns.GetHostAddresses(vars[0]);

            mClient = new TcpClient();
            mClient.Connect(ip, port);
            mStream = new NetworkStream(mClient.Client);
            mBuffer = new byte[BufferSize];

            if (Connected != null)
                Connected();

            BeginReceive();
        }

        /// <summary>
        /// Sends a packet to the realm
        /// </summary>
        /// <param name="packet"></param>
        public override void Send(BasicPacketOut packet)
        {
            byte[] output = packet.Finalize();

            if (PacketEncryption != null)
            {
                PacketEncryption.Encrypt(output, 0, 6);
            }

            mStream.Write(output, 0, output.Length);
            mStream.Flush();

            if (PacketSent != null && packet is RealmPacketOut)
                PacketSent(packet as RealmPacketOut);
        }

        protected override void OnReceive(IAsyncResult result)
        {
            int read = 0;
            try
            {
                read = mStream.EndRead(result);
            }
            catch
            {
                Disconnect();
                return;
            }

            if (read > 0)
            {
                int index = 0;

                if (mCurrentPacket == null)
                    mCurrentPacket = new RealmPacketIn();

                while (index != read)
                {
                    int lengthToWrite = 0;
                    int remaining = mCurrentPacket.ContentLength - (mCurrentPacket.Length - 2);

                    if (mCurrentPacket.HeaderRead)
                        lengthToWrite = Math.Min(read - index, remaining);
                    else
                    {
                        lengthToWrite = Math.Min(read - index, 2);
                    }

                    index += mCurrentPacket.Write(mBuffer, index, lengthToWrite);

                    if (!mCurrentPacket.HeaderRead && mCurrentPacket.Length >= mCurrentPacket.SizeLength)
                    {
                        mCurrentPacket.ContentLength = DecryptSize(mCurrentPacket);
                    }

                    if (mCurrentPacket.Length - 2 > 0 && mCurrentPacket.Length - 2 == mCurrentPacket.ContentLength)
                    {
                        mCurrentPacket.RawId = DecryptOpCode(mCurrentPacket);
                        mCurrentPacket.FinalizePacket();

                        if (!PacketHandler.IsHandledIngame(mCurrentPacket))
                            HandlePacket(mCurrentPacket);
                        else if (ActiveCharacter != null)
                            ActiveCharacter.QueuePacket(mCurrentPacket);

                        mCurrentPacket = new RealmPacketIn();
                    }
                }

                BeginReceive();
            }
            else
                Disconnect();
        }

        /// <summary>
        /// Decrypts the size of the incoming packet
        /// </summary>
        /// <param name="packet">Incoming packet</param>
        /// <returns>Size of the packet</returns>
        public int DecryptSize(RealmPacketIn packet)
        {
            if (PacketEncryption == null)
            {
                packet.BaseStream.Position = 0;
                return packet.ReadUInt16BE();
            }

            long position = packet.BaseStream.Position;

            packet.BaseStream.Position = 0;

            byte[] bytes = packet.ReadBytes(2);
            Decrypt(bytes, 0, 2);

            packet.BaseStream.Position = 0;
            packet.Write(bytes, 0, 2);

            packet.BaseStream.Position = 0;
            ushort r = packet.ReadUInt16BE();

            packet.BaseStream.Position = position;
            return r;
        }

        /// <summary>
        /// Decrypts the Op Code of the incoming packet
        /// </summary>
        /// <param name="packet">Incoming packet</param>
        /// <returns>Op code</returns>
        public int DecryptOpCode(RealmPacketIn packet)
        {
            if (PacketEncryption == null)
            {
                packet.BaseStream.Position = 2;
                return packet.ReadInt16();
            }

            packet.BaseStream.Position = 2;

            byte[] bytes = packet.ReadBytes(2);
            Decrypt(bytes, 0, 2);

            packet.BaseStream.Position = 2;
            packet.Write(bytes, 0, 2);

            packet.BaseStream.Position = 2;
            return packet.ReadInt16();
        }

        /// <summary>
        /// Decrypts a single byte in an array
        /// </summary>
        /// <param name="data">Array of bytes</param>
        /// <param name="index">Index of byte to be decrypted</param>
        /// <returns>Decrypted byte</returns>
        public byte DecryptByte(byte[] data, int index)
        {
            Decrypt(data, index, 1);

            return data[index];
        }

        /// <summary>
        /// Encrypts a series of bytes in an array
        /// </summary>
        /// <param name="data">Bytes to encrypt</param>
        /// <param name="start">Start position to encrypt</param>
        /// <param name="count">Length of data to encrypt</param>
        public void Encrypt(byte[] data, int start, int count)
        {
            PacketEncryption.Encrypt(data, start, count);
        }

        /// <summary>
        /// Decrypts a series of bytes in an array
        /// </summary>
        /// <param name="data">Bytes to decrypt</param>
        /// <param name="start">Start position to encrypt</param>
        /// <param name="count">Length of data to decrpyt</param>
        public void Decrypt(byte[] data, int start, int count)
        {
            PacketEncryption.Decrypt(data, start, count);
        }
    }
}
